package com.google.code.p.restrpc.core;

import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.google.code.p.restrpc.core.event.ServiceRegisteredEventObject;
import com.google.code.p.restrpc.event.RpcEventCenter;

/**
 * The local rpc-service-metadata repository
 * 
 * @author dz
 */
public class RpcServiceRepository implements RpcServiceVisitable {
    
    private static final ThreadLocal<RpcServiceMetadata> metadatas = new ThreadLocal<RpcServiceMetadata>();
    
    public static RpcServiceMetadata current() {
        return metadatas.get();
    }
    
    static void clear() {
        metadatas.set(null);
    }
    
    static void current(RpcServiceMetadata metadata) {
        metadatas.set(metadata);
    }
    
    private static final Log logger = LogFactory.getLog(RpcServiceRepository.class);
    
    private static class RpcServiceRepositoryHolder {
        
        private static final RpcServiceRepository instance = new RpcServiceRepository();
        
    }
    
    /**
     * @return
     */
    public static final RpcServiceRepository getInstance() {
        return RpcServiceRepositoryHolder.instance;
    }
    
    private ConcurrentHashMap<String, RpcServiceMetadata> services = new ConcurrentHashMap<String, RpcServiceMetadata>();
    
    private ConcurrentHashMap<String, RpcServiceProxy> proxies = new ConcurrentHashMap<String, RpcServiceProxy>();
    
    private boolean exported = false;
    
    private int weight = 1;
    
    /**
     * 
     */
    protected RpcServiceRepository() {
    }
    
    /**
     * @return
     */
    public int size() {
        return services.size();
    }
    
    /**
     * @return
     */
    public String[] getServiceNames() {
        return services.keySet().toArray(ArrayUtils.EMPTY_STRING_ARRAY);
    }
    
    /**
     * @param serviceName
     * @return
     */
    public RpcServiceMetadata getRpcServiceMetadata(String serviceName) {
        return services.get(serviceName);
    }
    
    /**
     * @param serviceName
     * @return
     */
    public boolean isRegistered(String serviceName) {
        return services.containsKey(serviceName);
    }
    
    /**
     * @param serviceName
     * @param service
     */
    public void registerService(RpcServiceMetadata metadata) {
        services.putIfAbsent(metadata.getServiceName(), metadata);
        //
        if (logger.isDebugEnabled()) {
            logger.debug("Registered: " + metadata);
        }
        //
        RpcEventCenter.getInstance()
                      .publishEvent(new ServiceRegisteredEventObject(metadata));
    }
    
    /**
     * @param serviceName
     * @param service
     */
    public void registerService(String serviceName,
                                Class serviceClass,
                                Class serviceInterface,
                                Object service,
                                Object target) {
        RpcServiceMetadata metadata = new RpcServiceMetadata(serviceName,
                                                             serviceClass,
                                                             serviceInterface,
                                                             service,
                                                             target);
        services.putIfAbsent(serviceName, metadata);
        //
        if (logger.isDebugEnabled()) {
            logger.debug("Registered: " + metadata);
        }
        //
        RpcEventCenter.getInstance()
                      .publishEvent(new ServiceRegisteredEventObject(metadata));
    }
    
    /**
     * @param serviceName
     * @return
     */
    public RpcServiceMetadata unregisterService(String serviceName) {
        RpcServiceMetadata result = services.remove(serviceName);
        // TODO if the service has been exported as remote service,this action
        // required to notify remote registry
        // result.unexport();
        return result;
    }
    
    /**
     * @param serviceName
     * @return
     */
    public RpcServiceProxy getServiceProxy(String serviceName) {
        RpcServiceProxy result = proxies.get(serviceName);
        if (result == null) {
            result = new DefaultRpcServiceProxy(services.get(serviceName));
            proxies.putIfAbsent(serviceName, result);
        }
        return result;
    }
    
    public boolean isExported() {
        return this.exported;
    }
    
    public void unexport() {
        this.exported = false;
    }
    
    public void export() {
        this.exported = true;
    }
    
    public int getWeight() {
        return weight;
    }
    
    public void setWeight(int weight) {
        if (weight < 1) {
            throw new IllegalArgumentException("The weight must be greater or equal to 1");
        }
        this.weight = weight;
    }
    
    /**
     * @param visitor
     */
    public void accept(RpcServiceVisitor visitor) {
        for (Iterator<RpcServiceMetadata> iterator = services.values()
                                                             .iterator(); iterator.hasNext();) {
            visitor.visit(iterator.next());
        }
    }
    
}
